package magnet.processor.instances.aspects.type;

import magnet.processor.MagnetProcessorEnv;
import magnet.processor.common.AptUtils;
import magnet.processor.common.ValidationException;
import magnet.processor.instances.parser.AttributeParser;
import magnet.processor.instances.parser.ParserInstance;

import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor6;
import java.util.ArrayList;
import java.util.List;

public class TypesAttributeParser extends AttributeParser {

    private static volatile TypesAttributeParser INSTANCE;

    public TypesAttributeParser() {
        super("types");
    }

    public static TypesAttributeParser INSTANCE() {
        synchronized (TypeAttributeParser.class) {
            if (INSTANCE == null) {
                synchronized (TypeAttributeParser.class) {
                    INSTANCE = new TypesAttributeParser();
                }
            }
        }
        return INSTANCE;
    }

    @Override
    public <E extends Element> ParserInstance<E> parse(Scope<E> scope, AnnotationValue value) throws ValidationException {
        MagnetProcessorEnv env = scope.getEnv();
        TypesExtractor typesExtractor = new TypesExtractor(env.getElements());
        value.accept(typesExtractor, null);
        List<TypeElement> interfaceTypeElements = typesExtractor.getExtractedValue();
        if (interfaceTypeElements == null) {
            return scope.getInstance();
        }

        if (scope.isTypeInheritanceEnforced()) {
            for (TypeElement typeElement : interfaceTypeElements) {
                AptUtils.verifyInheritance(typeElement, scope.getElement(), env.getTypes());
            }
        }

        ParserInstance instance = scope.getInstance().copy();
        instance.setDeclaredTypes(interfaceTypeElements);

        return instance;
    }

    private static class TypesExtractor extends SimpleAnnotationValueVisitor6<Void, Void> {
        private Elements elements;
        private List<String> extractedTypes = new ArrayList<>();
        public TypesExtractor(Elements elements) {
            this.elements = elements;
        }

        public List<TypeElement> getExtractedValue() {
            if (extractedTypes.isEmpty()) {
                return null;
            }
            List<TypeElement> result = new ArrayList<>();
            for (String item : extractedTypes) {
                result.add(elements.getTypeElement(item));
            }
            return result;
        }

        @Override
        public Void visitArray(List<? extends AnnotationValue> values, Void p) {
            if (values != null) {
                for (AnnotationValue value : values) {
                    value.accept(this, p);
                }
            }
            return p;
        }

        @Override
        public Void visitType(TypeMirror typeMirror, Void p) {
            if (typeMirror != null) {
                extractedTypes.add(typeMirror.toString());
            }
            return p;
        }
    }
}
